package com.lsj.service.impl;

import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.lsj.config.redis.RedisKey;
import com.lsj.core.exception.CustomerException;
import com.lsj.core.lock.DistributedLock;
import com.lsj.core.lock.LockKey;
import com.lsj.core.response.ResponseType;
import com.lsj.core.util.ExceptionHelperUtil;
import com.lsj.core.util.QueryHelperUtil;
import com.lsj.dto.sys.*;
import com.lsj.mapper.MySysMenuMapper;
import com.lsj.mapper.SysMenuMapper;
import com.lsj.mapper.SysRoleMenuMapper;
import com.lsj.mapper.entity.SysMenu;
import com.lsj.mapper.entity.SysRoleMenu;
import com.lsj.service.MenuService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author:lin.sj
 * @Data:2021/7/8 21:58
 * @Desc:
 */
@Service
@CacheConfig(cacheNames = RedisKey.USER_KEY)
public class MenuServiceImpl implements MenuService {
    @Autowired
    SysMenuMapper sysMenuMapper;
    @Autowired
    MySysMenuMapper mySysMenuMapper;
    @Autowired
    SysRoleMenuMapper sysRoleMenuMapper;

    /**
     * @Author: lin.sj
     * @Date: 2021/7/12 19:09
     * @Desc: 增加菜单，调用时需要使用分布式锁，保证插入的数据顺序唯一
     **/
    @Override
    @Transactional(rollbackFor = Exception.class)
    @DistributedLock(value = LockKey.menu_tree_key)
    public void addMenu(MenuAddDto dto) {
        //检查父级菜单是否存在
        SysMenu parentMenu = sysMenuMapper.selectById(dto.getMenuPid());
        if(parentMenu == null){
            throw new CustomerException(ResponseType.error,"父级菜单不存在，无法新增！");
        }else if(parentMenu.getIsLeaf()){
            //修改菜单为非叶子节点
            parentMenu.setIsLeaf(false);
            sysMenuMapper.updateById(parentMenu);
        }


        //获取当前level的菜单数量+1，即为新增菜单的sort
        int currentLevel = parentMenu.getLevel()+1;
        LambdaQueryWrapper<SysMenu> w = QueryHelperUtil.getLambdaWrapper(SysMenu.class);
        w.eq(SysMenu::getLevel,currentLevel).eq(SysMenu::getMenuPid,dto.getMenuPid());
        Integer sortCount = sysMenuMapper.selectCount(w)+1;

        //新增菜单
        SysMenu sysMenu = Convert.convert(SysMenu.class, dto);
        sysMenu
                //设置默认的菜单节点名称
                .setMenuName("新建菜单节点")
                //设置到根节点为止的父节点集合信息
                .setMenuPids(getMenuPids(dto.getMenuPid()))
                //设置可用
                .setStatus(true)
                //设置排序
                .setSort(sortCount)
                //设置为叶子节点
                .setIsLeaf(true)
                //父节点level+1,即为当前节点的level
                .setLevel(currentLevel);
        sysMenuMapper.insert(sysMenu);
    }

    /**
     * @Author: lin.sj
     * @Date: 2021/8/18 20:24
     * @Desc: 删除菜单
     * 菜单删除时，要保证树状结构的逻辑性和关联关系的逻辑性
     * 1.查询当前菜单下的所有子节点
     * 2.删除所有相关的菜单的关联关系(角色与菜单关系)
     * 3.删除删除所有相关的菜单信息
     * 4.检查并更新被删除节点的父节点的叶子状态信息(没有其他子节点则更新为叶子节点)
     **/
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(allEntries = true)
    @DistributedLock(value = LockKey.menu_tree_key)
    public void deleteMenuAndChildren(Long menuId) {
        //检查出该Id作为父节点的所有子节点
        LambdaQueryWrapper<SysMenu> w = QueryHelperUtil.getLambdaWrapper(SysMenu.class);
        w.like(SysMenu::getMenuPids,menuId);
        List<SysMenu> children = sysMenuMapper.selectList(w);

        //删除所有包含的角色与菜单关系（子菜单）,删除子菜单
        LambdaQueryWrapper<SysRoleMenu> roleMenuWhere = QueryHelperUtil.getLambdaWrapper(SysRoleMenu.class);
        for(SysMenu sysMenu:children){
            roleMenuWhere.clear();
            roleMenuWhere.eq(SysRoleMenu::getMenuId,sysMenu.getId());
            sysRoleMenuMapper.delete(roleMenuWhere);
            sysMenuMapper.deleteById(sysMenu.getId());
        }

        //删除所有包含的角色与菜单关系（当前菜单）
        roleMenuWhere.clear();
        roleMenuWhere.eq(SysRoleMenu::getMenuId,menuId);
        sysRoleMenuMapper.delete(roleMenuWhere);

        //重新排序当前删除层级的子节点
        //将排在后面的菜单往前挪,9999足够大了，不会有那么多的菜单
        SysMenu sysMenu = sysMenuMapper.selectById(menuId);
        updateTreeSort(sysMenu.getSort(),9999,sysMenu.getLevel(),sysMenu.getMenuPid());

        //删除当前菜单
        sysMenuMapper.deleteById(menuId);


        //删除后判断父菜单下是否还有节点
        w.clear();
        w.eq(SysMenu::getMenuPid,sysMenu.getMenuPid());
        Integer childrenCount = sysMenuMapper.selectCount(w);
        if(childrenCount <=0){
            //没有就更新父节点为叶子节点
            SysMenu preantMenu = sysMenuMapper.selectById(sysMenu.getMenuPid());
            preantMenu.setIsLeaf(false);
            sysMenuMapper.updateById(preantMenu);
        }
    }

    /**
     * @Author: lin.sj
     * @Date: 2021/8/18 20:27
     * @Desc: 更新菜单
     *     1.要把所有缓存在redis的用户信息都清除掉
     *     2.要有互斥锁，只能同时有一个人处理该数据，保证树的顺序不会出问题
     **/
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(allEntries = true)
    @DistributedLock(value = LockKey.menu_tree_key)
    public void updateMenu(MenuUpdateDto dto) {
        //检查当前数据是否存在
        SysMenu sysMenu = sysMenuMapper.selectById(dto.getId());
        ExceptionHelperUtil.nullObjException(sysMenu,"当前数据不存在，无法更新！");

        //检查是否存在相同的菜单名称或相同url的菜单
        LambdaQueryWrapper<SysMenu> w = QueryHelperUtil.getLambdaWrapper(SysMenu.class);
        w
                .ne(SysMenu::getId,dto.getId())
                .and(wrapper -> {
                    wrapper.eq(SysMenu::getMenuName,dto.getMenuName())
                            .or()
                            .eq(StringUtils.isNotBlank(dto.getUrl()),SysMenu::getUrl,dto.getUrl());
                })
                ;
        Integer repeatCount = sysMenuMapper.selectCount(w);
        if(repeatCount > 0){
            throw new CustomerException(ResponseType.error,"存在相同菜单名称或相同url的菜单，无法更新！");
        }

        //若当前菜单状态不可用时，需要删除role_menu表的关联关系,包括该菜单下的所有子节点,且需要更新该菜单下的节点都不可用
        if(!dto.getStatus()){
            //将当前节点id加入要删除的role_menu关系集合
            List<Long> roleMenuDeleteIds = new ArrayList<>();
            roleMenuDeleteIds.add(dto.getId());
            //非叶子节点获取所有子孙节点的ID集合
            if(!sysMenu.getIsLeaf()){
                w.clear();
                w.like(SysMenu::getMenuPids,dto.getId());
                List<SysMenu> childrenMenus = sysMenuMapper.selectList(w);
                //转换获取menuId
                List<Long> childIds = childrenMenus.stream().map(SysMenu::getId).collect(Collectors.toList());
                roleMenuDeleteIds.addAll(childIds);
                //设置子孙节点不可用
                childrenMenus.stream().forEach(m -> {
                    m.setStatus(false);
                    sysMenuMapper.updateById(m);
                });

            }
            //删除关联关系
            LambdaQueryWrapper<SysRoleMenu> roleMenuWhere = QueryHelperUtil.getLambdaWrapper(SysRoleMenu.class);
            roleMenuWhere.in(SysRoleMenu::getMenuId,roleMenuDeleteIds);
            sysRoleMenuMapper.delete(roleMenuWhere);
        }

        //设置状态可用时，叶子需要检查上级菜单是否可用，否则无法设置可用
        if(dto.getStatus() && sysMenu.getIsLeaf()){
            w.clear();
            w.eq(SysMenu::getId,sysMenu.getMenuPid());
            SysMenu parentMenu = sysMenuMapper.selectOne(w);
            if(!parentMenu.getStatus()){
                throw new CustomerException(ResponseType.error,"当前【"+sysMenu.getMenuName()+"】的上级节点不可用，请先对上级节点设置可用！");
            }
        }

        //将更新的内容复制到数据库实体
        BeanUtils.copyProperties(dto,sysMenu);
        int updateCount = sysMenuMapper.updateById(sysMenu);
        if(updateCount <=0){
            throw new CustomerException(ResponseType.error,"更新失败，请重新尝试！");
        }
    }

    /**
     * @Author: lin.sj
     * @Date: 2021/8/18 20:28
     * @Desc: 更新菜单排序
     **/
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(allEntries = true)
    @DistributedLock(value = LockKey.menu_tree_key)
    public void updateMenuSort(MenuSortDto menuSortDto) {
        if(menuSortDto.getOldSort() == 1 && menuSortDto.getOldSort() > menuSortDto.getNewSort()){
            throw new CustomerException(ResponseType.error,"菜单已经排在最前面了！");
        }
        SysMenu sysMenu = sysMenuMapper.selectById(menuSortDto.getId());

        LambdaQueryWrapper<SysMenu> w = QueryHelperUtil.getLambdaWrapper(SysMenu.class);
        w
                .eq(SysMenu::getMenuPid,sysMenu.getMenuPid());
        List<SysMenu> sysMenus = sysMenuMapper.selectList(w);

        //判断当前是否向下排序且修改前的排序与当前层级的数量一致，此时不再允许想后排序
        if(menuSortDto.getOldSort() < menuSortDto.getNewSort() && menuSortDto.getOldSort() == sysMenus.size()){
            throw new CustomerException(ResponseType.error,"菜单已经排在最后面了！");
        }
        //重新将菜单进行排序
        updateTreeSort(menuSortDto.getOldSort(),menuSortDto.getNewSort(),menuSortDto.getLevel(),sysMenus.get(0).getMenuPid());


        sysMenu.setSort(menuSortDto.getNewSort());
        sysMenuMapper.updateById(sysMenu);
    }

    /**
     * @Author: lin.sj
     * @Date: 2021/7/27 22:49
     * @Desc: 获取整个菜单树
     **/
    @Override
    public List<MenuTreeDto> getPositiveMenuTree() {
        SysMenu parentMenu = sysMenuMapper.selectById(0);
        MenuTreeDto parentMenuTree = Convert.convert(MenuTreeDto.class, parentMenu);
        loadMenuTree(parentMenuTree);
        return Arrays.asList(parentMenuTree);
    }
    /**
     * @Author: lin.sj
     * @Date: 2021/7/27 20:56
     * @Desc: 递归查找菜单信息，形成树菜单
     **/
    private List<MenuTreeDto> loadMenuTree(MenuTreeDto tree){
        //根据父菜单Id查找子菜单
        LambdaQueryWrapper<SysMenu> w = QueryHelperUtil.getLambdaWrapper(SysMenu.class);
        w.eq(SysMenu::getMenuPid,tree.getId()).orderByAsc(SysMenu::getSort);
        List<SysMenu> sysMenus = sysMenuMapper.selectList(w);

        List<MenuTreeDto> menuTreeDtos = new ArrayList<>();
        //循环子菜单
        for(SysMenu m:sysMenus){
            MenuTreeDto menu = Convert.convert(MenuTreeDto.class, m);
            //将当前的菜单加入到子菜单列表中
            menuTreeDtos.add(menu);
            //如果不是叶子节点，就递归查找下一级菜单信息
            if(!m.getIsLeaf()){
                List<MenuTreeDto> childMenus = loadMenuTree(menu);
                menu.setChildren(childMenus);
            }
        }
        //将当前的子菜单添加到父菜单中
        tree.setChildren(menuTreeDtos);
        return menuTreeDtos;
    }

    /**
     * @Author: lin.sj
     * @Date: 2021/7/13 22:20
     * @Desc: 根据用户Id获取对应的菜单信息(不包括根节点):
     * 1.获取所有的菜单数据totalMenus，不包括根节点
     * 2.计算菜单的层级
     * 3.根据层级进行循环，组成菜单树
     **/
    @Override
    public List<MenuTreeDto> getMenuTreeByUserId(Long userId) {
        //获取所有菜单数据
        List<SysMenu> totalMenus = mySysMenuMapper.getMenusByUserId(userId);
        //计算菜单的层级
        Set<Integer> levelSet = totalMenus.stream().map(SysMenu::getLevel).collect(Collectors.toSet());
        ArrayList<Integer> levels = new ArrayList<>(levelSet);
        //排序层级
        Collections.sort(levels);
        List<MenuTreeDto> returnMenuTrees = new ArrayList<>();
        Map<Long, MenuTreeDto> parentMap = new HashMap<>();
        int i = 1;
        for(Integer level:levels){
            List<MenuTreeDto> menuTrees = totalMenus.stream()
                    //找出相同层级的数据
                    .filter(m -> m.getLevel().equals(level))
                    //转换数据类型
                    .map(m -> {
                        MenuTreeDto menuTreeDto = Convert.convert(MenuTreeDto.class, m);
                        return menuTreeDto;
                    }).collect(Collectors.toList());

            //遍历上一个循环中放入的map中的,检查本层次是否有对应的pid
            Set<Long> pids = parentMap.keySet();
            for(Long pid:pids){
                //过滤pid相同的数据
                List<MenuTreeDto> children = menuTrees.stream().filter(m -> m.getMenuPid().equals(pid)).collect(Collectors.toList());
                //对本层级的数据进行排序
                children.sort((o1, o2) -> {
                    MenuTreeDto m1 = (MenuTreeDto)o1;
                    MenuTreeDto m2 = (MenuTreeDto)o2;
                    return m1.getSort() - m2.getSort();
                });
                MenuTreeDto parentMenu = parentMap.get(pid);
                parentMenu.setChildren(children);
            }
            //清空上个循环放入的数据，放入本次循环的数据
            parentMap.clear();
            menuTrees.stream().forEach(m -> parentMap.put(m.getId(),m));
            if(i == 1){
                //将第一个层级的数据加入到返回的数据中
                returnMenuTrees.addAll(menuTrees);
                i++;
            }
        }
        return returnMenuTrees;
    }



    /**
     * @Author: lin.sj
     * @Date: 2021/8/18 20:32
     * @Desc: 根据主键获取菜单信息
     **/
    @Override
    public MenuUpdateDto getMenuById(Long menuId) {
        SysMenu sysMenu = sysMenuMapper.selectById(menuId);
        ExceptionHelperUtil.nullObjException(sysMenu,"当前菜单信息不存在！");
        return Convert.convert(MenuUpdateDto.class,sysMenu);
    }

    /**
     * @Author: lin.sj
     * @Date: 2021/7/13 22:56
     * @Desc: 获得当前节点到根节点的菜单Id集合,返回包括入参Id的集合
     **/
    private String getMenuPids(Long menuPid){
        SysMenu sysMenu = sysMenuMapper.selectById(menuPid);
        List<Long> pids = getMenuPidsArray(sysMenu.getMenuPids());
        pids.add(menuPid);
        return pids.toString();
    }

    /**
     * @Author: lin.sj
     * @Date: 2021/7/13 22:53
     * @Desc: 将menuPids转换成集合返回，menuPids结构为[1,2,3,4...](公用方法)
     **/
    private List<Long> getMenuPidsArray(String menuPids){
        String[] pidsArray = menuPids.replace("[", "").replace("]", "").replace(" ","").split(",");
        List<String> pidsStrList = new ArrayList<>(Arrays.asList(pidsArray));
        List<Long> pids = pidsStrList.stream().map(pid -> Convert.toLong(pid)).collect(Collectors.toList());
        return pids;
    }

    /**
     * @Author: lin.sj
     * @Date: 2021/7/13 23:00
     * @Desc: 更新同级树节点下的排序，按以下规则：
     * 1.当前节点newSort>oldSort，将大于oldSort 和 小于等于newSort的同级节点排序-1
     * 2.当前节点newSort<oldSort，将大于等于newSort 和 小于oldSort的同级节点排序+1(公用方法)
     **/
    private void updateTreeSort(Integer oldSort,Integer newSort,Integer level,Long pid){
        LambdaUpdateWrapper w = new LambdaUpdateWrapper();
        if(newSort.intValue() == oldSort.intValue()){
            return;
        }else if(newSort.intValue() > oldSort.intValue()){
            w.setSql("sort = sort-1 where sort>"+oldSort+" and sort<="+newSort+" and level = "+level+" and menu_pid="+pid);
        }else if(newSort.intValue() < oldSort.intValue()){
            w.setSql("sort = sort+1 where sort>="+newSort+" and sort<"+oldSort+" and level = "+level+" and menu_pid="+pid);
        }
        sysMenuMapper.update(null,w);
    }
}
